import fg from "fast-glob";
import fs from "fs-extra";
import path from "path";
import { Project, ReferenceFindableNode } from "ts-morph";
import { readGB2312, writeGB2312 } from "./vendor.js";
import { toolchain } from "../toolchain.js";

export class ProtocolStriper {
    public async removeUseless(): Promise<void> {
        const project = new Project({
            tsConfigFilePath: path.join(toolchain.opts.projectRoot, 'TsScripts/tsconfig.json')
        });

        const outputFile = `tmp/${toolchain.opts.projectName}.protocolUsed.txt`;
        await this.findUsed(project, outputFile);

        const outContent = await fs.readFile(outputFile, 'utf-8');
        const lines = outContent.split(/\r?\n/);
        const usedItfs: string[] = [];
        for (const line of lines) {
            if (!line) continue;
            usedItfs.push(line);
        }

        await this.markItfDefinitions(usedItfs);
    }

    private async findUsed(project: Project, outFile: string): Promise<void> {
        if (!toolchain.opts.svrRoot) {
            console.error('no svr root');
            process.exit(1);
        }
        // Protocol.d.ts
        const usedItfs: string[] = [];
        const protocolFile = path.join(toolchain.opts.projectRoot, 'TsScripts/System/protocol/Protocol.d.ts');
        const gameConfigSrc = project.getSourceFile(protocolFile)!;
        const module = gameConfigSrc.getModuleOrThrow('Protocol');
        const interfaces = module.getInterfaces();
        for (const itf of interfaces) {
            let used = false;
            if (this.isNodeReferedOutside(itf)) {
                used = true;
            } else {
                // 检查字段是否被使用
                const properties = itf.getProperties();
                for (const property of properties) {
                    // if (property.getName() == 'm_iSkillPriority' && itf.getName() == 'SkillConfigM') {
                    //     console.log('this comes!');
                    // }
                    if (this.isNodeReferedOutside(property)) {
                        used = true;
                        break;
                    }
                }
            }
            if (used) {
                usedItfs.push(itf.getName());
            }
        }
        // ProtocolUtil.ts
        // 部分协议没有显式使用Request
        const protocolUtilFile = path.join(toolchain.opts.projectRoot, 'TsScripts/System/protocol/ProtocolUtil.ts');
        const protoclUtilContent = await fs.readFile(protocolUtilFile, 'utf-8');
        const mchs = protoclUtilContent.matchAll(/(?<=SendMsgUtil\.get).+(?=\(\))/g);
        for (const mch of mchs) {
            const request = mch[0];
            if (!usedItfs.includes(request)) {
                usedItfs.push(request);
            }
        }
        // 部分协议response也只是监听了msgid，并没有显式使用到msgbody
        await this.findByAddNetListener(usedItfs);

        await fs.writeFile(outFile, usedItfs.join('\n') + '\n', 'utf-8');
    }

    private async findByAddNetListener(out: string[]): Promise<void> {
        const tss = await fg('TsScripts/**/*.ts', { cwd: toolchain.opts.projectRoot });
        const usedMsgids: string[] = [];
        for (const ts of tss) {
            const tsFile = path.join(toolchain.opts.projectRoot, ts);
            const content = await fs.readFile(tsFile, 'utf-8');
            const mchs = content.matchAll(/(?<=addNetListener\(Macros.).+?(?=,)/g);
            for (const mch of mchs) {
                usedMsgids.push(mch[0]);
            }
        }
        // 获取对应的response
        const csXml = path.join(toolchain.opts.svrRoot, 'CS.xml');
        const csContent = await fs.readFile(csXml, 'utf-8');
        const mchs = csContent.matchAll(/<entry\s+name=\s*"\w+"\s+type\s*=\s*"(\w+)"\s+value\s*=\s*"(\w+)"/g);
        for (const mch of mchs) {
            const rspntf = mch[1];
            const msgid = mch[2];
            if (usedMsgids.includes(msgid) && !out.includes(rspntf)) {
                out.push(rspntf);
            }
        }
    }

    private isNodeReferedOutside(node: ReferenceFindableNode, ): boolean {
        const excludes = ['DecodeBase.ts', 'DecodeUtil.ts', 'EncodeUtil.ts', 'SendMsgUtil.ts'];
        const refSymbols = node.findReferences();
        let used = false;
        for (const refSymbol of refSymbols) {
            for (const ref of refSymbol.getReferences()) {
                if (ref.isDefinition()) continue;
                // console.log(`${refSymbol.getDefinition().getName()} refered in ${ref.getSourceFile().getFilePath()}`);
                const bn = ref.getSourceFile().getBaseName();
                if (!excludes.includes(ref.getSourceFile().getBaseName())) {
                    used = true;
                    break;
                }
            }
            if (used) break;
        }
        return used;
    }

    private async markItfDefinitions(usedItfs: string[]): Promise<void> {
        const xmlFiles = ['Common.xml', 'CS.xml', 'ProtoType.xml'];
        for (const x of xmlFiles) {
            const xmlFile = path.join(toolchain.opts.svrRoot, x);
            const xmlContent = await readGB2312(xmlFile);
            const lines = xmlContent.split(/\r?\n/);

            let modified = false;
            for (let i = 0, len = lines.length; i < len; i++) {
                let line = lines[i];
                const originalLine = line;
                const mch = line.match(/^\s*<struct\s+name\s*=\s*"(\w+)".* (?:convertOption\s*=\s*"(\w+)")?/);
                if (mch != null) {
                    // struct定义开始
                    const theItf = mch[1];
                    // 只标记一级字段
                    // if (!theItf.endsWith('Notify') && !theItf.endsWith('Response') && !theItf.endsWith('Request')) continue;
                    const convertOption = mch[2]?.split(',');
                    if (usedItfs.includes(theItf)) {
                        // 添加标记
                        if (!convertOption?.includes('clientUsed')) {
                            if (line.includes('convertOption')) {
                                line = originalLine.replace(/convertOption\s*=\s*"/, `convertOption="clientUsed,`);
                            } else {
                                line = originalLine.replace('>', ' convertOption="clientUsed">');
                            }
                            lines[i] = line;
                            modified = true;
                        }
                    } else {
                        // 去除标记
                        if (convertOption?.includes('clientUsed')) {
                            line = originalLine.replace('clientUsed', '').replace('convertOption=""', '');
                            lines[i] = line;
                            modified = true;
                        }
                    }
                }
            }
            if (modified) await writeGB2312(xmlFile, lines.join('\n'));
        }
    }
}
